package ar.utn.jess.common;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import ar.utn.jess.exception.DataException;

public class DateUtil {
	
	private static SimpleDateFormat sqlDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	private static SimpleDateFormat timestampDateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm");
	private static SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
	

	public static Date createDate(int day, int month, int year) {
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.DAY_OF_MONTH, day);
		cal.set(Calendar.MONTH, month);
		cal.set(Calendar.YEAR, year);
		return startOfDay(cal.getTime());
	}

	public static Date startOfDay(Date date) {
		Calendar cal = Calendar.getInstance();
		cal.setTime(date);
		cal.set(Calendar.HOUR, 0);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		return cal.getTime();
	}

	public static Date getDefaultStartDate() {
		return DateUtil.createDate(1, Calendar.SEPTEMBER, 1940);
	}

	public static Date normalizeStartDate(Date startDate) {
		Date ret = null;
		if (startDate == null) {
			ret = getDefaultStartDate();
		} else {
			Calendar cal = Calendar.getInstance();
			cal.setTime(startDate);
			cal.set(Calendar.DAY_OF_MONTH, 1);
			cal.set(Calendar.MONTH, Calendar.SEPTEMBER);
			ret = startOfDay(cal.getTime());
		}
		return ret;
	}

	public static Date normalizeEndDate(Date endDate) {
		Date ret = null;
		if (endDate != null) {
			Calendar cal = Calendar.getInstance();
			cal.setTime(endDate);
			cal.set(Calendar.DAY_OF_MONTH, 31);
			cal.set(Calendar.MONTH, Calendar.AUGUST);
			ret = startOfDay(cal.getTime());
		}
		return ret;
	}

	/**
	 * @return Adds the given number of days to the given date and return a
	 *         copy.
	 */
	public static Date addDays(Date date, int days) {

		GregorianCalendar gc = new GregorianCalendar();
		gc.setTime(date);
		gc.add(Calendar.DAY_OF_MONTH, days);

		return gc.getTime();
	}

	/**
	 * @return Adds the given number of days to the given date and return a
	 *         copy.
	 */
	public static Date addYears(Date date, int years) {

		GregorianCalendar gc = new GregorianCalendar();
		gc.setTime(date);
		gc.add(Calendar.YEAR, years);

		return gc.getTime();
	}

	/** Should return true if 1 or more days from 1 period lie in the other period.
	 *  ( start1 <= end2 and start2 <= end1 )
	 * @param from1
	 * @param until1
	 * @param from2
	 * @param until2
	 * @return
	 */
	public static boolean isOverlapping(Date from1, Date until1, Date from2, Date until2) {
		from1 = stripTime(from1);
		until1 = stripTime(until1);
		from2 = stripTime(from2);
		until2 = stripTime(until2);
		
		return (from1 == null || until2 == null || from1.compareTo(until2) <= 0)
				&& (from2 == null || until1 == null || from2.compareTo(until1) <= 0);
	}

	/**
	 * Returns true if the given date is between (or equal to one of) start- and enddate.
	 * If startdate is null or enddate is null, the period is not limited in that direction.
	 * (For example: 1/09/2000 between 1/09/1900 and NULL will return TRUE)
	 */
	public static boolean isInPeriod(Date startdate, Date enddate, Date date) {
		startdate = stripTime(startdate);
		enddate = stripTime(enddate);
		date = stripTime(date);
		if ( 	( startdate == null || startdate.compareTo(date) <= 0 ) 
			 && ( enddate == null || enddate.compareTo(date) >= 0 ) ) {
			return true;
		}
		return false;
	}

	/** Sets the time of day to 'undefined', only keeps the date
	 */
	public static Date stripTime(Date date) {
		if (date != null) {
			Calendar cal1 = Calendar.getInstance();
			cal1.setTime(date);
			Calendar cal2 = Calendar.getInstance();
			cal2.clear();
			cal2.set( cal1.get(Calendar.YEAR), cal1.get(Calendar.MONTH), cal1.get(Calendar.DAY_OF_MONTH) );
	
			return cal2.getTime();
		}
		else {
			return null;
		}
	}
	
	public static int getYear( Date d ) {
		Calendar cal = Calendar.getInstance();
		cal.setTime( d );
		
		return cal.get(Calendar.YEAR);
	}

	public static int getMonth( Date d ) {
		Calendar cal = Calendar.getInstance();
		cal.setTime( d );
		
		return cal.get(Calendar.MONTH);
	}

	public static int getDayOfMonth( Date d ) {
		Calendar cal = Calendar.getInstance();
		cal.setTime( d );
		
		return cal.get(Calendar.DAY_OF_MONTH);
	}
	
	/**
	 * Converts a date to its sql representation
	 */
	public static String dateToSql(Date date) {
		return sqlDateFormat.format(date);
	}
	
	public static String stringDateToSql(String str) {
		Date date = stringToDate(str);
		return dateToSql(date);
	}

	public static String dateToStringWithTime(Date date) {	
		return timestampDateFormat.format(date);
	}
	
	public static String dateToString(Date date) {	
		return dateFormat.format(date);
	}
	
	public static Date sqlStringToDate(String str) {
		Date result = null;
		if(str != null && !str.isEmpty()) {
			try {
				result = sqlDateFormat.parse(str);
			}
			catch(ParseException e) {
				throw new DataException(e);
			}
		}
		
		return result;
	}
	
	public static Date stringToDate(String str) {
		Date result = null;
		if(str != null && !str.isEmpty()) {
			try {
				result = dateFormat.parse(str);
			}
			catch(ParseException e) {
				throw new DataException(e);
			}
		}
		
		return result;
	}
	
	public static boolean isValidMonthDay(Integer month, Integer day) {
		boolean result = true;
		
		if(month > 12)
			result = false;
		else {
			if(month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) {
				result = (day <= 31);				
			}
			else if(month == 4 || month == 6 || month == 9 || month == 11) {
				result = (day <= 30);
			}
			else if(month == 2) {
				result = (day <= 28); //ignore leap years
			}
		}
		
		return result;
	}
	
	public static Date addOneDay(Integer year, Integer month, Integer day) {
		if(year < 1900) {
			if(year < 80)
				year += 2000;
			else
				year += 1900;
		}
			
		Calendar cal = Calendar.getInstance();
		cal.clear();
		cal.set(year, month-1, day);
		
		cal.add(Calendar.DAY_OF_MONTH, 1);
		return cal.getTime();
	}
	
	
	public static Integer getDaysBetweenTwoDates(Date startDate, Date endDate) {
		Integer result = 0;
		
		if(startDate.compareTo(endDate) > 0)
			return null;
				
		while(startDate.compareTo(endDate) < 0) {
			startDate = DateUtil.addDays(startDate, 1);
			result++;			
		}
		
		return result;
	}
	
}